package org.dei.perla.sys.device.adapter;

import java.util.ArrayList;

import org.dei.perla.sys.device.channel.frame.address.FrameAddress;
import org.dei.perla.utils.messages.Message;
import org.dei.perla.utils.pipe.Pipe;

/**
 * Classe che gestisce la memorizzazione dei canali virtuali 
 * e degli indirizzi fisici, utilizzando la classe AdapterSrvEntry come struttura dati.
 * 
 * @see org.dei.perla.sys.device.adapter.AdapterSrvEntry
 */
public class AdapterSrvRoutingTable {
	
	/**
	 * Lista di tutti i device e di tutti i percorsi virtuali che
	 * servono per collegarli alle rispettive FPC
	 */
	private ArrayList<AdapterSrvEntry> pVcList;
	
	/**
	 * Adapter Server
	 */
	private AdapterServer pAdSrv;
	
	/**
	 * Costruttore
	 * 
	 * @param adSrv, l'Adapter Server
	 */
	public AdapterSrvRoutingTable(AdapterServer parAdSrv){
		
		this.pVcList = new ArrayList<AdapterSrvEntry>();
		
		this.pAdSrv = parAdSrv;
		
	}
	
	/**
	 * Aggiunge un device al registro
	 * 
	 * @param parBvci, canale di binding (temporaneo)
	 * @param parUpperPipeIn, pipe su cui ricevere dati dal livello superiore
	 * @param parUpperPipeOut, su cui mandare dati dal livello superiore
	 * @param parLowerPipeIn, pipe di lettura dal livello inferiore
	 * @param parLowerPipeOut, pipe di scrittura sdal livello inferiore
	 * @param parDeviceAdd, indirizzo del dispositivo
	 * @param parAdapterAddress, indirizzo dell'adapter
	 * @return true se l'operazione è eseguita correttamente
	 * 	       false se c'è stato qualche errore
	 */
	public boolean addEntry(int parBvci, 
			Pipe<Message> parUpperPipeIn, 
			  Pipe<Message> parUpperPipeOut, 
			   Pipe<Message> parLowerPipeIn, 
			  	Pipe<Message> parLowerPipeOut, 
			  	 FrameAddress parDeviceAdd,
			  	  FrameAddress parAdapterAddress){
		
		AdapterSrvEntry tNewDevice = 
							new AdapterSrvEntry(parBvci, 
											     parUpperPipeIn, 
											      parUpperPipeOut, 
											       parLowerPipeIn, 
											        parLowerPipeOut, 
											         parDeviceAdd,
											          parAdapterAddress);
		
		if (tNewDevice != null){
			
			// Controlla se esiste già un elemento uguale da inserire
			for(int i = 0;i < this.pVcList.size(); i++){
				
				if((tNewDevice.getBvci() == this.pVcList.get(i).getBvci()) &&
					tNewDevice.getDeviceAddress().equals(this.pVcList.get(i).getDeviceAddress())){
					
					return false;
					
				}
				
			}
			
			this.pVcList.add(tNewDevice);
			return true;
		}
		
		return false;
	
	}
	
	
	/**
	 * Aggiunge un device al registro
	 * 
	 * @param parBvci, canale di binding (temporaneo)
	 * @param parUpperPipeIn, pipe su cui ricevere dati dal livello superiore
	 * @param parUpperPipeOut, su cui mandare dati dal livello superiore
	 * @param parLowerPipeIn, pipe di lettura dal livello inferiore
	 * @param parLowerPipeOut, pipe di scrittura sdal livello inferiore
	 * @param parDeviceAdd, indirizzo del dispositivo
	 * @return true se l'operazione è eseguita correttamente
	 * 	       false se c'è stato qualche errore
	 */
	public boolean addEntry(int parBvci, 
			Pipe<Message> parUpperPipeIn, 
			  Pipe<Message> parUpperPipeOut, 
			   Pipe<Message> parLowerPipeIn, 
			  	Pipe<Message> parLowerPipeOut, 
			  	 FrameAddress parDeviceAdd){
		
		AdapterSrvEntry tNewDevice = new AdapterSrvEntry(parBvci, parUpperPipeIn, parUpperPipeOut, parLowerPipeIn, parLowerPipeOut, parDeviceAdd);
		
		if (tNewDevice != null){
			
			// Controlla se esiste già un elemento uguale da inserire
			for(int i = 0;i < this.pVcList.size(); i++){
				
				if((tNewDevice.getBvci() == this.pVcList.get(i).getBvci()) &&
					tNewDevice.getDeviceAddress().equals(this.pVcList.get(i).getDeviceAddress())){
					
					return false;
					
				}
				
			}
			
			this.pVcList.add(tNewDevice);
			return true;
		}
		
		return false;
	
	}
	
	/**
	 * Setta il nuovo canale virtuale e lo memorizza
	 * 
	 * @param parBvci, identificatore del canale
	 * @param parAddress, indirizzo sorgente del device da registrare
	 * @return il nuovo identificatore del canale
	 */
	public int setNewVci(int parBvci, FrameAddress parAddress){
		
		int tNewVci = pAdSrv.pCreateUniqueChannelId();
		
		AdapterSrvEntry tEl = this.pGetElFromBvciAndDeviceAddress(parBvci, parAddress);
		
		tEl.setVci(tNewVci);
		
		return tNewVci;
	}
	
	/**
	 * Trova l'identificatore del canale virtuale relativo 
	 * alla pipe del livello superiore passata come parametro
	 * 
	 * @param parPipe, pipe di input dal livello superiore
	 * @return identificatore del canale cercato,
	 * 		   0 se non trova riscontro
	 */
	public int getVciFromUpperPipeIn(Pipe<Message> parPipe){
		
		for(int i = 0; i < pVcList.size(); i++){
			
			AdapterSrvEntry el = pVcList.get(i);
			
			if (el.getUpperPipeIn().equals(parPipe)){  
				
				if(el.getVci() == 0) 
						return el.getBvci();
				else 
					return el.getVci();
			
			}
			
		}
		
		return 0;
	}
	
	/**
	 * Trova l'identificatore del canale virtuale relativo 
	 * alla pipe del livello superiore passata come parametro
	 * 
	 * @param parPipe, pipe di output verso il livello superiore
	 * @return identificatore del canale cercato
	 * 		   0 se non trova riscontro
	 */
	public int getVciFromUpperPipeOut(Pipe<Message> parPipe){
		
	for(int i = 0; i < pVcList.size(); i++){
			
			AdapterSrvEntry el = pVcList.get(i);
			
			if (el.getUpperPipeOut().equals(parPipe)){  
				
				if(el.getVci() == 0) 
						return el.getBvci();
				else 
					return el.getVci();
			
			}
			
		}
		
		return 0;
	}
	
	/**
	 * Trova la pipe del livello superiore relativa all'identificatore
	 * del canale virtuale passato come parametro
	 * 
	 * @param parIdentifier, identificatore del canale da cercare
	 * @return pipe di output cercata
	 * 		   null se non trova riscontro
	 */
	@SuppressWarnings("unchecked")
	public Pipe<Message>[] getUpperPipesFromVci(int parIdentifier){
		
		Pipe<Message>[] pipes = new Pipe[2];
		
		pipes[0] = this.pGetElFromVci(parIdentifier).getUpperPipeIn();
		pipes[1] = this.pGetElFromVci(parIdentifier).getUpperPipeOut();
		
		return pipes;
	}
	
	/**
	 * Trova la pipeOut del livello superiore e inferiore relativa all'identificatore
	 * del canale virtuale passato come parametro
	 * 
	 * @param parIdentifier, identificatore del canale da cercare
	 * @return pipe di output cercata
	 * 		   null se non trova riscontro
	 */
	@SuppressWarnings("unchecked")
	public Pipe<Message>[] getPipesOutFromVci(int parIdentifier){
		
		Pipe<Message>[] pipes = new Pipe[2];
		
		pipes[0] = this.pGetElFromVci(parIdentifier).getUpperPipeOut();
		pipes[1] = this.pGetElFromVci(parIdentifier).getLowerPipeOut();
		
		return pipes;
	}
	
	/**
	 * Trova la pipeIn del livello superiore e inferiore relativa all'identificatore
	 * del canale virtuale passato come parametro
	 * 
	 * @param parIdentifier, identificatore del canale da cercare
	 * @return pipe di output cercata
	 * 		   null se non trova riscontro
	 */
	@SuppressWarnings("unchecked")
	public Pipe<Message>[] getPipesInFromVci(int parIdentifier){
		
		Pipe<Message>[] pipes = new Pipe[2];
		
		pipes[0] = this.pGetElFromVci(parIdentifier).getUpperPipeIn();
		pipes[1] = this.pGetElFromVci(parIdentifier).getLowerPipeIn();
		
		return pipes;
	}
	
	/**
	 * Trova la pipe del livello superiore relativa all'identificatore
	 * del canale virtuale di binding passato come parametro e all'indirizzo
	 * del del device
	 * 
	 * @param parIdentifier, identificatore del canale da cercare
	 * @param parAddress, indirizzo del device
	 * @return pipe di output cercata
	 * 		   null se non trova riscontro
	 */
	@SuppressWarnings("unchecked")
	public Pipe<Message>[] getUpperPipesFromBvciAndDeviceAddress(int parIdentifier, FrameAddress parAddress){
		
		Pipe<Message>[] pipes = new Pipe[2];
		
		pipes[0] = this.pGetElFromBvciAndDeviceAddress(parIdentifier, parAddress).getUpperPipeIn();
		pipes[1] = this.pGetElFromBvciAndDeviceAddress(parIdentifier, parAddress).getUpperPipeOut();
		
		return pipes;
	}

	/**
	 * Trova l'identificatore del canale virtuale relativo 
	 * alla pipe del canale passata come parametro
	 * 
	 * @param parPipe, pipe di input da cercare
	 * @return identificatore del canale cercato,
	 * 		   0 se non trova riscontro
	 */
	public int getVciFromLowerPipeIn(Pipe<Message> parPipe){
		
		for(int i = 0; i < pVcList.size(); i++){
			
			AdapterSrvEntry el = pVcList.get(i);
			
			if (el.getLowerPipeIn().equals(parPipe)){  
				
				if(el.getVci() == 0) 
						return el.getBvci();
				else 
					return el.getVci();
			
			}
			
		}
		
		return 0;
	}
	
	/**
	 * Trova l'identificatore del canale virtuale relativo 
	 * alla pipe del canale passata come parametro
	 * 
	 * @param parPipe, pipe di output da cercare
	 * @return identificatore del canale cercato
	 * 		   0 se non trova riscontro
	 */
	public int getVciFromLowerPipeOut(Pipe<Message> parPipe){
		
	for(int i = 0; i < pVcList.size(); i++){
			
			AdapterSrvEntry el = pVcList.get(i);
			
			if (el.getLowerPipeOut().equals(parPipe)){  
				
				if(el.getVci() == 0) 
						return el.getBvci();
				else 
					return el.getVci();
			
			}
			
		}
		
		return 0;
	}
	
	/**
	 * Trova la pipe del canale relativa all'identificatore
	 * di canale virtuale passato come parametro
	 * 
	 * @param parIdentifier, identificatore del canale da cercare
	 * @return pipe di output cercata
	 * 		   null se non trova riscontro
	 */
	@SuppressWarnings("unchecked")
	public Pipe<Message>[] getLowerPipesFromVci(int parIdentifier){
		
		Pipe<Message>[] pipes = new Pipe[2];
		
		pipes[0] = this.pGetElFromVci(parIdentifier).getLowerPipeIn();
		pipes[1] = this.pGetElFromVci(parIdentifier).getLowerPipeOut();
		
		return pipes;
	}
	
	/**
	 * Trova la pipe del canale relativa all'identificatore
	 * di canale virtuale di binding passato come parametro
	 * 
	 * @param parIdentifier, identificatore del canale da cercare
	 * @param parAddress, indirizzo del canale da cercare
	 * @return pipe di output cercata
	 * 		   null se non trova riscontro
	 */
	@SuppressWarnings("unchecked")
	public Pipe<Message>[] getLowerPipesFromBvciAndDeviceAddress(int parIdentifier, FrameAddress parAddress){
		
		Pipe<Message>[] pipes = new Pipe[2];
		
		pipes[0] = this.pGetElFromBvciAndDeviceAddress(parIdentifier, parAddress).getLowerPipeIn();
		pipes[1] = this.pGetElFromBvciAndDeviceAddress(parIdentifier, parAddress).getLowerPipeOut();
		
		return pipes;
	}	
	
	/**
	 * Rimuove un oggetto dal registro conoscendo il suo BVCI
	 * 
	 * @param parBvci, canale dell'oggeto da rimuovere
	 * @return true se l'oggetto è stato rimosso,
	 * 		   false se l'oggetto non era presente
	 */
	public boolean removeEntryByBvci(int parBvci){
		
		for(int i = 0; i < pVcList.size(); i++){
			
			AdapterSrvEntry el = pVcList.get(i);
			
			if (el.getBvci() == parBvci) 
				
				return pVcList.remove(el);
			
		}
		
		return false;
		
	}
	
	/**
	 * Rimuove un oggetto dal registro conoscendo il suo VCI
	 * 
	 * @param parVci, canale dell'oggeto da rimuovere
	 * @return true se l'oggetto è stato rimosso,
	 * 		   false se l'oggetto non era presente
	 */
	public boolean removeEntryByVci(int parVci){
		
		for(int i = 0; i < pVcList.size(); i++){
			
			AdapterSrvEntry el = pVcList.get(i);
			
			if (el.getVci() == parVci) 
				
				return pVcList.remove(el);
			
		}
		
		return false;
		
	}
	
	/**
	 * Trova un elemento del registro dal suo identificatore di canale di binding e
	 * dal suo device address
	 * 
	 * @param parIdentifier, identificatore del canale
	 * @param parAddress, indirizzo del device
	 * @return l'elemento cercato,
	 * 		   null se non è stato trovato nulla
	 */
	private AdapterSrvEntry pGetElFromBvciAndDeviceAddress(int parIdentifier, FrameAddress parAddress){
		
		for(int i = 0; i < pVcList.size(); i++){
			
			AdapterSrvEntry el = pVcList.get(i);
			
			if ((el.getBvci() == parIdentifier) &&
				 el.getDeviceAddress().equals(parAddress)) 
				
					return el;
			
		}
		
		return null;
	}
	
	/**
	 * Trova un elemento del registro dal suo identificatore di canale
	 * 
	 * @param parIdentifier, identificatore del canale
	 * @return l'elemento cercato,
	 * 		   null se non è stato trovato nulla
	 */
	private AdapterSrvEntry pGetElFromVci(int parIdentifier){
		
		for(int i = 0; i < pVcList.size(); i++){
			
			AdapterSrvEntry el = pVcList.get(i);
			
			if (el.getVci() == parIdentifier) return el;
			
		}
		
		return null;
	}
	
	/**
	 * Ritorna l'indirizzo del dispositivo dato il suo VCI
	 * 
	 * @param parVci, identificatore del canale virtuale
	 * @return l'indirizzo
	 */
	public FrameAddress getDeviceAddressFromVci(int parVci){
		
		return this.pGetElFromVci(parVci).getDeviceAddress();
		
	}
	
	/**
	 * Ritorna la lista di entry
	 * 
	 * @return la lista
	 */
	public ArrayList<AdapterSrvEntry> getAllEntries(){
		
		return this.pVcList;
	}
	
	/**
	 * Aggiorna il l'identificatore del canale VCI nella tabella di routing
	 * sapendo il Bvci e l'indirizzo del device
	 * 
	 * @param parVci, nuovo valore vci
	 * @param parBvci, vecchio canale di binding
	 * @param parDeviceAddr, indirizzo del device
	 */
	public void setVciFromBvciAndDeviceAddress(int parVci, int parBvci, FrameAddress parDeviceAddr){
		
		this.pGetElFromBvciAndDeviceAddress(parBvci, parDeviceAddr).setVci(parVci);
		
	}
	
	/**
	 * Aggiorna il l'identificatore del canale VCI nella tabella di routing
	 * sapendo il Bvci rimappato e l'indirizzo del device
	 * 
	 * @param parVci, nuovo valore vci
	 * @param parBvci, vecchio canale di binding
	 * @param parDeviceAddr, indirizzo del device
	 */
	public void setVciFromBvciRemappedAndAdapterAddress(int parVci, int parBvci, FrameAddress parAdapterAddr){
		
		AdapterSrvEntry el;
		
		for(int i = 0; i < this.pVcList.size(); i++){
			
			el = this.pVcList.get(i);
			
			if((el.getRemappedBvci() == parBvci) &&
				el.getAdapterAddress().equals(parAdapterAddr)) {
				
				el.setVci(parVci);
				
			}
			
		}
		
	}
	
	/**
	 * Rimuove un oggetto dal registro conoscendo il suo BVCI e il suo indirizzo
	 * 
	 * @param parBvci, canale dell'oggeto da rimuovere
	 * @param parDeviceAddr, inidirizzo del dispositivo
	 * @return true se l'oggetto è stato rimosso,
	 * 		   false se l'oggetto non era presente
	 */
	public boolean removeEntryByBvciAndDeviceAddress(int parBvci, FrameAddress parDeviceAddr){
		
		return this.pVcList.remove(this.pGetElFromBvciAndDeviceAddress(parBvci, parDeviceAddr));
		
	}
	
	/**
	 * Da un nuovo bvci, univoco su questa rete, al messaggio di binding.
	 * @param parRemappedBvci, nuovo bvci
	 * @param parBvci, bvci vecchio
	 * @param parDeviceAddr, indirizzo fisico del device
	 */
	public void setRemappedBvciFromBvciAndDeviceAddress(int parRemappedBvci, int parBvci, FrameAddress parDeviceAddr){
		
		this.pGetElFromBvciAndDeviceAddress(parBvci, parDeviceAddr).setRemappedBvci(parRemappedBvci);
		
	}
	
	/**
	 * Trova il bvci vero sapendo il nuovo vci
	 * @param parVci, nuovo identificatore di canale
	 * @return vecchio bvci
	 */
	public int getBvciFromVci(int parVci){
		
		return this.pGetElFromVci(parVci).getBvci();
		
	}
	
	/**
	 * Trova l'inidirizzo del device sapendo il bvci rimappato e l'indirizzo del device
	 * @param parRemappedBvci, bvci rimappato
	 * @param parAdapterAddress, indirizzo dell'adapter
	 * @return vecchio bvci
	 */
	public FrameAddress getDeviceAddressFromBvciRemappedAndAdapterAddress(int parRemappedBvci, FrameAddress parAdapterAddr){
		
		for(int i = 0; i < this.pVcList.size(); i++){
			
			AdapterSrvEntry el = this.pVcList.get(i);
			
			if((el.getRemappedBvci() == parRemappedBvci) &&
					el.getAdapterAddress().equals(parAdapterAddr)) { 
				
				return el.getDeviceAddress();
				
			}
			
		}
		
		return null;
		
	}
	
	/**
	 * Trova il bvci vero sapendo il bvci rimappato e l'indirizzo del device
	 * @param parRemappedBvci, bvci rimappato
	 * @param parAdapterAddress, indirizzo dell'adapter
	 * @return vecchio bvci
	 */
	public int getBvciFromBvciRemappedAndAdapterAddress(int parRemappedBvci, FrameAddress parAdapterAddr){
		
		for(int i = 0; i < this.pVcList.size(); i++){
			
			AdapterSrvEntry el = this.pVcList.get(i);
			
			if((el.getRemappedBvci() == parRemappedBvci) &&
					el.getAdapterAddress().equals(parAdapterAddr)) { 
				
				return el.getBvci();
				
			}
			
		}
		
		return 0;
		
	}
	
	/**
	 * Trova l'indirizzo dell'adapter relativo al vci
	 * @param parVci, vci
	 * @return indirizzo dell'adapter relativo
	 */
	public FrameAddress getAdapterAddressFromVci(int parVci){
		
		return this.pGetElFromVci(parVci).getAdapterAddress();
		
	}
	

}